import { Injectable } from '@nestjs/common';
import { Across, Bet, Tower } from '@prisma/client';
import { max } from 'lodash';
import { PrismaService } from 'src/prisma/prisma.service';
import {
  calcAngle,
  calcF,
  calcFB,
  calcHi,
  calcTAFromTB,
  calcTBFromFB,
} from 'src/util/ljMath';

@Injectable()
export class CalculatePowerAcrossService {
  constructor(private prisma: PrismaService) {}

  async calculate(config: Record<string, any>) {
    const { zoneId, configId } = config;

    // 1.获取所有控制点
    const zone = await this.prisma.zone.findFirst({
      where: { id: +zoneId },
      include: {
        bets: true,
        towers: true,
        across: {
          include: {
            bet: true,
          },
        },
      },
    });

    let { bets, towers, across } = zone;
    const powerConfig = await this.prisma.calcPowerConfig.findFirst({
      where: { id: +configId },
      include: {
        DaoWire: true,
      },
    });

    // bets.reverse();
    const { DaoWire, e, order } = powerConfig;

    /**
     * !如果方向是反的
     * ?那么Bets要进行反转
     * */
    let { lst_bet, lst_across } = this.reverseModels(order, bets, across);

    // 计算受力
    const lst_tower_power = lst_across.map((ac) => {
      const { btName, acrossX, acrossY, controlHeight } = ac;
      const bet = lst_bet.find((b) => b.btName === btName);
      const x = acrossX;
      const y = acrossY + controlHeight;
      const btSpan = bet.btSpan;
      const [t1, t2] = bet.btName.split('--');
      const tower1 = towers.find((t) => t.tName === t1);
      const tower2 = towers.find((t) => t.tName === t2);
      const h1 = tower1.altitude;
      const h2 = tower2.altitude;
      const h = h2 - h1;
      const angle = calcAngle(h, btSpan);
      const w = DaoWire.wireWeight;

      const Hi = calcHi(w, x, btSpan, h1, y, angle); // 水平张力
      const F = calcF(btSpan, w, Hi, angle); // 最大弧垂
      const FB = calcFB(F, h); // B点的平视弧垂
      const TT = this.calculateAcrossTT(
        w,
        e,
        Hi,
        FB,
        angle,
        h,
        ac.btName,
        towers,
        lst_bet,
      ); //出口张力

      return {
        configId: +configId,
        acrossId: ac.id,
        Hi,
        F,
        FB,
        TT,
      };
    });

    await this.prisma.calcPowerAcross.createMany({ data: lst_tower_power });

    // 返回张力整定值
    const TT = max(lst_tower_power.map((t) => t.TT));
    return { TT };
  }

  async findAll(args: Record<string, any>) {
    return await this.prisma.calcPowerAcross.findMany({
      where: { configId: +args.configId },
      include: { across: true },
    });
  }

  private reverseModels(order: boolean, bets: Bet[], acrosss: Across[]) {
    let lst_bet: Bet[] = bets;
    let lst_across: Across[] = acrosss;

    if (order) return { lst_bet, lst_across };

    lst_bet = bets.reverse().map((bet) => {
      const [tn1, tn2] = bet.btName.split('--');
      bet.btName = `${tn2}--${tn1}`;
      return bet;
    });
    lst_across = acrosss.map((a) => {
      const [tn1, tn2] = a.btName.split('--');
      a.btName = `${tn2}--${tn1}`;
      const bet = lst_bet.find((b) => b.btName === a.btName);
      a.acrossX = bet.btSpan - a.acrossX;
      return a;
    });

    return { lst_bet, lst_across };
  }

  /**
   * !计算出口张力
   */
  private calculateAcrossTT(
    w: number,
    e: number,
    Hi: number,
    FB: number,
    angle: number,
    h: number,
    btName: string,
    towers: Tower[],
    bets: Bet[],
  ) {
    const index = bets.findIndex((b) => b.btName === btName);
    // 计算第index档内TB,再计算第index档内TA
    let TB = calcTBFromFB(Hi, w, FB, angle);
    let TA = calcTAFromTB(TB, w, h, angle);

    const TT = this.calculateTA(TA, index - 1, w, e, towers, bets);
    return TT;
  }

  /**
   * !采用递归法计算TA
   */
  private calculateTA(
    T: number,
    index: number,
    w: number,
    e: number,
    towers: Tower[],
    bets: Bet[],
  ) {
    if (index < 0) {
      return T;
    }

    const TB = T / e;

    const [tname1, tname2] = bets[index].btName.split('--');
    const tower1 = towers.find((t) => t.tName == tname1);
    const tower2 = towers.find((t) => t.tName == tname2);
    const h = tower2.altitude - tower1.altitude;
    const angle = calcAngle(h, bets[index].btSpan);

    const TA = calcTAFromTB(TB, w, h, angle);
    return this.calculateTA(TA, --index, w, e, towers, bets);
  }
}
